// Refernece: 
//   1. AN2659
//   2. https://github.com/Zepan/hairBoot
//   3. http://bbs.21ic.com/icview-1251224-1-1.html

#include <iostm8s103f3.h>
//#include <intrinsics.h>

#define UART1_FLAG_TXE  (unsigned char)0x80  // Tx Data Register Empty flag
#define UART1_FLAG_RXNE (unsigned char)0x20  // Rx Data Register Not Empty flag

#define BLOCK_BYTES   64
#define	BLOCK_SHIFT   6
#define BOOT_ERR      0xa1
#define BOOT_HEAD     0xa5
#define BOOT_OK       0xa0
#define BOOT_WRITE    0xa7
#define BOOT_WRITE_INTERRUPT_VEC 0xaa
#define FLASH_START   0x8000
//#define INIT_PAGE     0x08

void UART1_SendB(unsigned char ch)
{
  while (!(UART1_SR & UART1_FLAG_TXE));
  UART1_DR = ch;
}

unsigned char UART1_RcvB(void)
{
  while(!(UART1_SR & UART1_FLAG_RXNE));
  return ((unsigned char)UART1_DR);
}

//addr must at begin of block
__ramfunc void FLASH_ProgBlock(unsigned char *addr, unsigned char *Buffer)
{
  unsigned char i;
  FLASH_CR2 |= (unsigned char)0x01;     // programming time fixed at Tprog
  FLASH_NCR2 &= (unsigned char)(0xFE);  // block programming enabled
  // Copy data bytes from RAM to FLASH memory
  for (i=0; i<BLOCK_BYTES; i++)
    *((__near unsigned char*) (unsigned int)addr + i) = ((unsigned char)(Buffer[i]));
}

int main( void )
{
  unsigned int tryCnt = 65535;
  unsigned char ch, page;
  unsigned char i = 100;  //10 for 1s
  unsigned char buf[BLOCK_BYTES];
  unsigned char verify;
  unsigned char *addr;

  // setup clock
  CLK_CKDIVR &= (unsigned char)(~0x18);  // 16MHz high speed internal clock

  //LED: B5
  PB_DDR = 0x20;  // output
  //PB_CR1 = 0x20;  // push pull
  //PB_CR2 |= 0x20;  // 10MHz output speed
  
  // setup uart: 115200, 8-n-1
  UART1_CR2 = 0x0C;  // rx/tx enabled, rx int disabled, tx int disabled
  UART1_BRR2 = 0x0B;  // 115200 baud rate. must set BRR2 first
  UART1_BRR1 = 0x08;  // 115200 baud rate. must set BRR2 first
  
  // wait UART data
  while (i) {
    PB_ODR = i<<5;  // LOW is ON
    if (UART1_SR & UART1_FLAG_RXNE) {  //wait for head
      ch = (unsigned char)UART1_DR;
      if (ch == BOOT_HEAD)
        break;
    }
    tryCnt--;
    if (tryCnt == 0) i--;
  }
  //PB_ODR |= 0x20;  // HIGH if OFF
  
  if (i == 0) {
    FLASH_IAPSR &= (unsigned char)0xFD;  // lock flash
    asm("JP $8200");
  }
  else {
    FLASH_PUKR = (unsigned char)0x56;  // unlock flash
    FLASH_PUKR = (unsigned char)0xAE;  // unlock flash
    UART1_SendB(0xa8);
    while (1) {
      ch = UART1_RcvB();
      switch (ch) {
      
      case BOOT_WRITE:
      case BOOT_WRITE_INTERRUPT_VEC:
        page = UART1_RcvB();
        addr = (unsigned char*)(FLASH_START + (page << BLOCK_SHIFT));
        verify = 0;
        for(i = 0; i < BLOCK_BYTES; i++) {
          buf[i] = UART1_RcvB();
          verify += buf[i];
        }
        if (verify == UART1_RcvB()) {  // verify successed
          if (page == 0 && ch == BOOT_WRITE_INTERRUPT_VEC) { // restore original reset interrupt vec
            for (i = 0; i < 4; i++)
              buf[i] = addr[i];
          }
          FLASH_ProgBlock(addr, buf);
          UART1_SendB(BOOT_OK);
          break;
        }
        
        default:  // verify failed
          UART1_SendB(BOOT_ERR);
          break;        
        
      }
    }
  }  
}
